using System;
using System.Collections.Generic;
using System.IO;
using System.Net;

namespace MiFi {
	/// <summary>
	/// Encapsulates a MiFi device at an IP address.
	/// </summary>
	public class MiFiDevice {
		/// <summary>
		/// The device's IP address.
		/// </summary>
		public IPAddress IPAddress { get; set; }

		/// <summary>
		/// Creates a new MiFiDevice class encapsulating the device at the given IP address.
		/// </summary>
		/// <param name="ipAddress">IP address of the device.</param>
		public MiFiDevice(IPAddress ipAddress) {
			if (ipAddress == null)
				throw new ArgumentNullException("ipAddress");

			IPAddress = ipAddress;
		}

		/// <summary>
		/// Retrieves the device's status information.
		/// </summary>
		/// <returns>Status information.</returns>
		public MiFiStatus GetStatus() {
			MiFiStatus status = new MiFiStatus();

			string data = Request("getStatus.cgi?dataType=TEXT");
			var items = Parse(data);

			foreach (var item in items) {
				string value = item.Value;

				switch (item.Key) {
					case "WwNtwkFound":
						status.NetworkFound = value == "1";
						break;
					case "WwNetwkName":
						status.NetworkName = value;
						break;
					case "WwNetwkTech":
						status.NetworkTechnology = value;
						break;
					case "WwRoaming":
						status.RoamingStatus = (RoamingStatus)int.Parse(value);
						break;
					case "WwRssi":
						status.SignalStrength = int.Parse(value);
						break;
					case "WwConnStatus":
						status.ConnectionStatus = (ConnectionStatus)int.Parse(value);
						break;
					case "WwSessionTxMb":
						status.DataTransmitted = double.Parse(value);
						break;
					case "WwSessionRxMb":
						status.DataReceived = double.Parse(value);
						break;
					case "WwSessionTimeSecs":
						status.SessionTime = TimeSpan.FromSeconds(double.Parse(value));
						break;
					case "WwIpAddr":
						status.IPAddress = IPAddress.Parse(value);
						break;
					case "WwMask":
						status.Mask = IPAddress.Parse(value);
						break;
					case "WwGateway":
						status.Gateway = IPAddress.Parse(value);
						break;
					case "WwDNS1":
						status.Dns = IPAddress.Parse(value);
						break;
					case "BaBattStat":
						status.Charge = int.Parse(value);
						break;
					case "BaBattChg":
						status.Charging = value == "1";
						break;
					case "WiConnClients":
						status.Clients = int.Parse(value);
						break;
					case "WwActivation":
						status.ActivationStatus = (ActivationStatus)int.Parse(value);
						break;
					case "WwPrl":
						status.PrlUpdateStatus = (PrlUpdateStatus)int.Parse(value);
						break;
					case "WwFumo":
						status.FirmwareUpdateStatus = (FirmwareUpdateStatus)int.Parse(value);
						break;
					case "MslStatus":
						status.MasterSubsidyLock = value == "1";
						break;
				}
			}

			return status;
		}

		/// <summary>
		/// Retrieves the device's current GPS data.
		/// </summary>
		/// <returns>Current GPS data.</returns>
		public MiFiLocation GetLocation() {
			MiFiLocation location = new MiFiLocation();

			string data = Request("getlastfix.cgi?dataType=TEXT");
			var items = Parse(data);

			foreach (var item in items) {
				string value = item.Value;

				switch (item.Key) {
					case "altitude":
						location.Altitude = double.Parse(value);
						break;
					case "angle_uncertainty":
						location.AngleOfUncertainty = double.Parse(value);
						break;
					case "heading":
						location.Heading = double.Parse(value);
						break;
					case "horizontal_velocity":
						location.HorizontalVelocity = double.Parse(value);
						break;
					case "latitude":
						location.Latitude = double.Parse(value);
						break;
					case "longitude":
						location.Longitude = double.Parse(value);
						break;
					case "perpendicular_std_dev_uncertainty":
						location.PerpendicularUncertainty = double.Parse(value);
						break;
					case "std_dev_uncertainty":
						location.StandardDeviationUncertainty = double.Parse(value);
						break;
					case "timestamp_str":
						location.Timestamp = DateTime.Parse(value);
						break;
					case "vertical_std_dev_uncertainty":
						location.VerticalUncertainty = double.Parse(value);
						break;
					case "vertical_velocity":
						location.VerticalVelocity = double.Parse(value);
						break;
					case "horizontal_accuracy":
						location.HorizontalAccuracy = double.Parse(value);
						break;
					case "vertical_accuracy":
						location.VerticalAccuracy = double.Parse(value);
						break;
					case "status":
						location.HasFix = value == "1";
						break;
				}
			}

			return location;
		}

		private string Request(string path) {
			try {
				HttpWebRequest request = (HttpWebRequest)WebRequest.Create(string.Format("http://{0}/{1}", IPAddress, path));
				using (StreamReader response = new StreamReader(request.GetResponse().GetResponseStream())) {
					return response.ReadToEnd();
				}
			} catch (Exception ex) {
				throw new MiFiException("Failed to communicate with MiFi device.", ex);
			}
		}

		private const char LINE_SEPARATOR = (char)27; /* ESCAPE */
		private const char PAIR_SEPARATOR = '=';

		private static Dictionary<string, string> Parse(string data) {
			var items = new Dictionary<string, string>();
			string[] lines = data.Split(LINE_SEPARATOR);

			foreach (string line in lines) {
				string[] pair = line.Split(PAIR_SEPARATOR);

				if (pair.Length > 1) {
					items.Add(pair[0], pair[1]);
				}
			}

			return items;
		}
	}
}